﻿/**
* ...
* @author Default
*/

package hails;

import config.DatabaseConfig;
import controller.UserController;
import php.db.Connection;
import php.db.Object;
import php.db.ResultSet;
import php.db.Mysql;
import Type;

class HailsDbRecord {
	public var id:Int;
	
	public function new() {
		
	}
	
	private var _fieldNames:List<String>;
	
	public function initFromResult(dbResult : Dynamic) : Void {
		if (dbResult == null) {
			// New record
			return;
		}
		var resFields:Array<String> = Reflect.fields(dbResult);
		var thisFields:Array<String> = Reflect.fields(this);
		this._fieldNames = new List<String>();
		for (i in 0...resFields.length) {
			if (arrayContains(thisFields, resFields[i])) {
				Reflect.setField(this, resFields[i], Reflect.field(dbResult, resFields[i]));
				this._fieldNames.add(resFields[i]);
			}
		}
	}
	
	public function getFieldNames() : List < String > {
		if (this._fieldNames == null) {
			var thisFields:Array<String> = Reflect.fields(this);
			this._fieldNames = new List<String>();
			for (fn in thisFields) {
				if (fn.charAt(0) != '_') {
					var field:Dynamic = Reflect.field(this, fn);
					if (!Reflect.isFunction(field)) {
						// It's not a field starting with "_" and it's not a function, so assume db-field:
						this._fieldNames.add(fn);
					}
				}
			}
		}
		return this._fieldNames;
	}
	
	public function isNew() : Bool {
		return (this.id == null);
	}
	
	public function fieldValue(fname:String) : Dynamic {
		return Reflect.field(this, fname);
	}
	
	private function arrayContains(arr:Array < String > , s:String) : Bool {
		for (i in 0...arr.length) {
			if (arr[i] == s) {
				return true;
			}
		}
		return false;
	}

	private static function tableNameForClass < T > (c:Class < T > ) : String {
		return hails.util.StringUtil.tableize(hails.util.StringUtil.removePackageNameFromClassName(Type.getClassName(c)));
	}
	
	public function tableName() : String {
		return tableNameForClass(Type.getClass(this));
	}
	
	public static function findById < T > (c:Class < T > , id:Int) : T {
		var recs:List < T > = findBy(c, "id", id);
		if (recs != null) {
			return recs.first();
		}  else {
			return null;
		}
	}
	
	public static function findBy < T > (c:Class < T > , field:String, val:Dynamic) : List < T > {
		//trace("      VAL = ");
		//trace(val.toString());
		var op:String = " = ";
		if (Type.typeof(val) == TClass(String)) {
			var s:String = val;
			if (StringTools.startsWith(s, '%') || StringTools.endsWith(s, '%')) {
				op = " LIKE ";
			}
		}
		// val.toString() works with haxe 2.01 but not with haxe 2.02
		var whereString = " WHERE " + field + op + "'" + val /*.toString()*/ + "'"; 
		return findByWhere(c, whereString);				
	}
	
	public static function findAll < T > (c:Class < T > ) : List < T > {
		return findByWhere(c, "");
	}
	
	public static function findByWhere < T > (c:Class < T > , whereString:String) : List < T > {
		var tableName:String = tableNameForClass(c);
		var res:ResultSet = runSql("SELECT * FROM " + tableName + " " + whereString);
		var ret:List<T> = new List<T>();
		while (res.hasNext()) {
			var rec = Type.createInstance(c, []);
			var initFunc:Dynamic<T> = Reflect.field(rec, 'initFromResult');
			//ret.add(Type.createInstance(c, [res.next()]));
			Reflect.callMethod(rec, initFunc, [res.next()]);
			ret.add(rec);
		}
		return ret;
	}
	
	public static function findAssociated < T , U > (rec:T, hasMany:Class < U > ) : List<U> {
		var foreignKey:String = tableNameForClass(Type.getClass(rec)) + "_id";
		return findBy(hasMany, foreignKey, Reflect.field(rec, "id"));
	}
	
	public function findAssociatedRecords<U>(hasMany:Class < U > ) : List < U > {
		return findAssociated(this, hasMany);
	}
	
	public static function findParent < T, U > (rec:T, parentClass:Class < U > ) : U {
		var foreignKey:String = tableNameForClass(parentClass).toLowerCase() + "_id";
		var parentId:Int = Reflect.field(rec, foreignKey);
		return findById(parentClass, parentId);
	}
	
	public function findOwnerRecord<U>(ownerClass:Class < U > ) : U {
		return findParent(this, ownerClass);
	}
	
	private static var connection:Connection;
	
	public static function createConnection() : Connection {
		/*if (connection == null) {
			trace("CONNECTION IS NULL");
			trace(connection);*/
		  var connection = Mysql.connect( 
			{ user : DatabaseConfig.user,
				socket : DatabaseConfig.socket,
				pass : DatabaseConfig.password,
				host : DatabaseConfig.host,
				port : DatabaseConfig.port,
			database : DatabaseConfig.database } );
		/*}
			trace("CONNECTION IS NOT NULL");
			trace(connection);*/
		return connection;
	}
	
	public static function closeConnection() {
		if (connection != null) {
			connection.close();
		}
	}
	
	private function escapeValue(val:Dynamic) : Dynamic {
		// ! TODO !
		trace("<" + val + ">");
		return val;
	}
	
	public static function runSql(sql:String) : ResultSet {
		var conn:Connection = createConnection();
		var res = conn.request(sql);
		conn.close();
		return res;
	}
	
	public static function runSqlAndReturnId(sql:String) : Int {
		var conn:Connection = createConnection();
		var res = conn.request(sql);
		var lastId:Int = conn.lastInsertId();
		conn.close();
		return lastId;
	}
	
	
	public function insert() : Void {
		var sql:String = "INSERT INTO " + tableName();
		var fnString:String = "";
		var valString:String = "";
		var first:Bool = true;
		for (fn in getFieldNames()) {
			if (fn != "id") {
				var val:Dynamic = fieldValue(fn);
				if (val == false) {
					val = '0';
				}
				if (!first) {
					fnString += ",";
					valString += ",";
				}
				fnString += fn;
				if (val != null) {
					valString += "'" + escapeValue(val) + "'";
				} else {
					valString += "NULL";
				}
				first = false;
			}
		}
		sql += " (" + fnString + ") VALUES (" + valString + ")";
		this.id = runSqlAndReturnId(sql);
	}
	
	public function update() : Void {
		var sql:String = "UPDATE " + tableName() + " SET ";
		var updateString:String = "";
		var first:Bool = true;
		for (fn in getFieldNames()) {
			if (fn != "id") {
				var val:Dynamic = fieldValue(fn);
				if (val == false) {
					val = '0';
				}
				if (!first) {
					updateString += ",";
				}
				if (val != null) {
					updateString += fn + " = '" + escapeValue(val) + "' ";
				} else {
					updateString += fn + " = NULL ";
				}
				first = false;
			}
		}
		sql += updateString + " WHERE id = '" + this.id + "'";
		runSql(sql);
	}
	
	public function save() : Void {
		if (isNew()) {
			insert();
		} else {
			update();
		}
	}
}